package cn.hollis.nft.turbo.pay.application.service;

import cn.hollis.nft.turbo.api.collection.constant.CollectionSaleBizType;
import cn.hollis.nft.turbo.api.collection.request.CollectionSaleRequest;
import cn.hollis.nft.turbo.api.collection.response.CollectionSaleResponse;
import cn.hollis.nft.turbo.api.collection.service.CollectionFacadeService;
import cn.hollis.nft.turbo.api.collection.service.CollectionManageFacadeService;
import cn.hollis.nft.turbo.api.order.service.OrderFacadeService;
import cn.hollis.nft.turbo.api.order.constant.OrderErrorCode;
import cn.hollis.nft.turbo.api.order.model.TradeOrderVO;
import cn.hollis.nft.turbo.api.order.request.OrderPayRequest;
import cn.hollis.nft.turbo.api.order.response.OrderResponse;
import cn.hollis.nft.turbo.api.pay.constant.PayErrorCode;
import cn.hollis.nft.turbo.api.pay.request.RefundCreateRequest;
import cn.hollis.nft.turbo.base.exception.BizException;
import cn.hollis.nft.turbo.base.response.SingleResponse;
import cn.hollis.nft.turbo.base.utils.MoneyUtils;
import cn.hollis.nft.turbo.base.utils.RemoteCallWrapper;
import cn.hollis.nft.turbo.pay.domain.entity.PayOrder;
import cn.hollis.nft.turbo.pay.domain.entity.RefundOrder;
import cn.hollis.nft.turbo.pay.domain.event.PaySuccessEvent;
import cn.hollis.nft.turbo.pay.domain.event.RefundSuccessEvent;
import cn.hollis.nft.turbo.pay.domain.service.PayOrderService;
import cn.hollis.nft.turbo.pay.domain.service.RefundOrderService;
import cn.hollis.nft.turbo.pay.infrastructure.channel.common.request.RefundChannelRequest;
import cn.hollis.nft.turbo.pay.infrastructure.channel.common.response.RefundChannelResponse;
import cn.hollis.nft.turbo.pay.infrastructure.channel.common.service.PayChannelServiceFactory;
import cn.hutool.core.lang.Assert;
import com.alibaba.fastjson.JSON;
import io.seata.spring.annotation.GlobalTransactional;
import io.seata.tm.api.transaction.TransactionHookManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;

@Service
@Slf4j
public class PayApplicationService {

    private static final String REFUND_MEMO_PREFIX = "重复支付退款：";


    @Autowired
    private PayOrderService payOrderService;

    @Autowired
    private OrderFacadeService orderFacadeService;

    @Autowired
    private CollectionFacadeService collectionFacadeService;

    @Autowired
    private CollectionManageFacadeService collectionManageFacadeService;

    @Autowired
    private RefundOrderService refundOrderService;

    @Autowired
    @Lazy
    private PayChannelServiceFactory payChannelServiceFactory;

    @Autowired
    protected TransactionTemplate transactionTemplate;

    /**
     * 支付成功
     * <pre>
     *     正常支付成功：
     *     1、查询订单状态
     *     2、推进订单状态到支付成功
     *     3、藏品库存真正扣减
     *     4、创建持有的藏品
     *     5、推进支付状态到支付成功
     *     6、持有的藏品上链
     *
     *     支付幂等成功：
     *      1、查询订单状态
     *      2、推进支付状态到支付成功
     *
     *      重复支付：
     *      1、查询订单状态
     *      2、创建退款单
     *      3、重试退款直到成功
     * </pre>
     */

    @GlobalTransactional
    public boolean paySuccess(PaySuccessEvent paySuccessEvent){
        log.info("----订单支付业务流程,开启分布式全局事务，支付成功后查询订单状态");
        PayOrder payOrder = payOrderService.queryByOrderId(paySuccessEvent.getPayOrderId());
        log.info("----订单支付业务流程,1.支付成功后查询支付订单状态,支付单号:{},订单号:{},支付单状态:【{}】",payOrder.getPayOrderId(),payOrder.getBizNo(),payOrder.getOrderState());
        if (payOrder.isPaid()) {
            return true;
        }

        OrderPayRequest orderPayRequest = getOrderPayRequest(paySuccessEvent, payOrder);
        OrderResponse orderResponse = RemoteCallWrapper.call(req -> orderFacadeService.pay(req), orderPayRequest, "orderFacadeService.pay",false);
        log.info("----订单支付业务流程,2.推进订单状态到已支付,orderResponse:{}",orderResponse);
        if (orderResponse.getResponseCode() != null && orderResponse.getResponseCode().equals(OrderErrorCode.ORDER_ALREADY_PAID.getCode())) {
            log.info("order already paid ,do chargeback ," + payOrder.getBizNo());
            Boolean result = payOrderService.paySuccess(paySuccessEvent);
            Assert.isTrue(result, () -> new BizException(PayErrorCode.PAY_SUCCESS_NOTICE_FAILED));
            doChargeBack(paySuccessEvent);
            return true;
        }
        if (!orderResponse.getSuccess()) {
            log.error("orderFacadeService.pay error, response = {}", JSON.toJSONString(orderResponse));
            return false;
        }



        SingleResponse<TradeOrderVO> response = orderFacadeService.getTradeOrder(payOrder.getBizNo());
        TradeOrderVO tradeOrderVO = response.getData();
        CollectionSaleRequest collectionSaleRequest = getCollectionSaleRequest(tradeOrderVO);


        CollectionSaleResponse collectionSaleResponse = RemoteCallWrapper.call(req -> collectionFacadeService.confirmSale(req), collectionSaleRequest, "collectionFacadeService.confirmSale");
        log.info("----订单支付业务流程,3&4.藏品库存真正扣减&创建持有的藏品,collectionSaleResponse:{}",collectionSaleResponse);


        Boolean result = payOrderService.paySuccess(paySuccessEvent);
        log.info("----订单支付业务流程,5.更新支付订单状态为成功,paySuccessEvent:{},result:{}",paySuccessEvent,result);
        Assert.isTrue(result, () -> new BizException(PayErrorCode.PAY_SUCCESS_NOTICE_FAILED));



        TransactionHookManager.registerHook(new PaySuccessTransactionHook(collectionSaleResponse.getHeldCollectionId()));
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean refundSuccess(RefundSuccessEvent refundSuccessEvent) {
        RefundOrder refundOrder = refundOrderService.queryByOrderId(refundSuccessEvent.getRefundOrderId());
        if (refundOrder.isRefunded()) {
            return true;
        }

        boolean refundResult = payOrderService.refundSuccess(refundSuccessEvent)
                && refundOrderService.refundSuccess(refundSuccessEvent);

        Assert.isTrue(refundResult, () -> new BizException(PayErrorCode.REFUND_SUCCESS_NOTICE_FAILED));

        return true;
    }


    private static CollectionSaleRequest getCollectionSaleRequest(TradeOrderVO tradeOrderVO) {
        CollectionSaleRequest collectionSaleRequest = new CollectionSaleRequest();
        collectionSaleRequest.setCollectionId(Long.valueOf(tradeOrderVO.getGoodsId()));
        collectionSaleRequest.setIdentifier(tradeOrderVO.getOrderId());
        collectionSaleRequest.setUserId(tradeOrderVO.getBuyerId());
        collectionSaleRequest.setQuantity((long) tradeOrderVO.getItemCount());
        collectionSaleRequest.setBizNo(tradeOrderVO.getOrderId());
        collectionSaleRequest.setBizType(CollectionSaleBizType.PRIMARY_TRADE.name());
        collectionSaleRequest.setName(tradeOrderVO.getGoodsName());
        collectionSaleRequest.setCover(tradeOrderVO.getGoodsPicUrl());
        collectionSaleRequest.setPurchasePrice(tradeOrderVO.getItemPrice());
        return collectionSaleRequest;
    }

    private static OrderPayRequest getOrderPayRequest(PaySuccessEvent paySuccessEvent, PayOrder payOrder) {
        OrderPayRequest orderPayRequest = new OrderPayRequest();
        orderPayRequest.setOperateTime(paySuccessEvent.getPaySucceedTime());
        orderPayRequest.setPayChannel(paySuccessEvent.getPayChannel());
        orderPayRequest.setPayStreamId(payOrder.getPayOrderId());
        orderPayRequest.setAmount(paySuccessEvent.getPaidAmount());
        orderPayRequest.setOrderId(payOrder.getBizNo());
        orderPayRequest.setOperatorType(payOrder.getPayerType());
        orderPayRequest.setOperator(payOrder.getPayerId());
        orderPayRequest.setIdentifier(payOrder.getPayOrderId());
        return orderPayRequest;
    }

    /**
     * 退款方法
     * @param paySuccessEvent
     */
    private void doChargeBack(PaySuccessEvent paySuccessEvent) {
        RefundCreateRequest refundCreateRequest = new RefundCreateRequest();
        refundCreateRequest.setIdentifier(paySuccessEvent.getChannelStreamId());
        refundCreateRequest.setMemo(REFUND_MEMO_PREFIX + paySuccessEvent.getChannelStreamId());
        refundCreateRequest.setPayOrderId(paySuccessEvent.getPayOrderId());
        refundCreateRequest.setRefundAmount(paySuccessEvent.getPaidAmount());
        refundCreateRequest.setRefundChannel(paySuccessEvent.getPayChannel());
        RefundOrder refundOrder = refundOrderService.create(refundCreateRequest);
        Assert.notNull(refundOrder, () -> new BizException(PayErrorCode.REFUND_CREATE_FAILED));

        //异步进行退款执行，失败了交给定时任务重试
        Thread.ofVirtual().start(() -> {
            RefundChannelRequest refundChannelRequest = new RefundChannelRequest();
            refundChannelRequest.setRefundOrderId(refundOrder.getRefundOrderId());
            refundChannelRequest.setPaidAmount(MoneyUtils.yuanToCent(refundOrder.getPaidAmount()));
            refundChannelRequest.setPayChannelStreamId(refundOrder.getPayChannelStreamId());
            refundChannelRequest.setPayOrderId(refundOrder.getPayOrderId());
            refundChannelRequest.setRefundAmount(MoneyUtils.yuanToCent(refundOrder.getApplyRefundAmount()));
            refundChannelRequest.setRefundReason(refundOrder.getMemo());

            RefundChannelResponse refundChannelResponse = payChannelServiceFactory.get(paySuccessEvent.getPayChannel()).refund(refundChannelRequest);

            if (refundChannelResponse.getSuccess()) {
                refundOrderService.paying(refundOrder.getRefundOrderId());
            }
        });
    }
}
